package exteditapp.command;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;

import exteditapp.FileDefault;
import exteditapp.state.StateError;
import exteditapp.state.StateIdling;
import exteditapp.state.StateIdlingLocked;
import exteditapp.state.StateIdlingUpdated;
import exteditapp.state.IState;

/**
 * Tries to determine whether file was changed in Plone CMS or not.
 * 
 * Uses plone python scripts on server side. Scripts can be accessed from file address, just with
 * some special extension to the address. First script tries to connect to server and check date of
 * last modification. It compares given date with the one, which is saved if application parameters
 * database (was sent to script with PARAM atributes on page load and can be not up to date). After
 * date check we checking whether file is locked or not. According to given date and lock data we
 * change current script state into IdlingUpdated, IdlingLocked or just Idling.
 * @author Artūras Šmorgun, arturas@smorgun.lt
 * @version 2009.03.24
 */
public class CommandCheck extends ICommand {

	/**
	 * Default constructor
	 */
	public CommandCheck(FileDefault file){
		super(file);
	}

	/**
	 * Execution of is updated procedure to plone
	 * @return true on success and false otherwise
	 */
	public Boolean execute() {
		
		/**
		 * Begin of execution.
		 */
		
		execution = true;
		
		/**
		 * Saving current state and it's parameters.
		 * State value will be changed only if file modifications leads to change in current
		 * file state. In other case - it will be leaved same as it was before. We also save
		 * current state label and progress, because while check is executed it will be chan-
		 * ged to "UPDATING...", and should be restored on completion of check.
		 */
		
		IState currentState = file.getCurrentState();
		String currentLabel = currentState.getLabel();
		int currentProgress = currentState.getProgress();
		currentState.setLabel("UPDATING...");
		currentState.setProgress(1);	
		
		/**
		 * Shared connection's variables.
		 * anyChanges is a trigger, which shows if there was some changes in current file state.
		 * For instance, it is most useful then we are checking modification date - if it is mo-
		 * dified, we can just change trigger position and save new modification date freely.
		 * retryNumber used only in case of network timeout. It shows us how much retries was
		 * made. Default value for maximum retries number for now is 5. In future it will be set
		 * in flobal system parameters in ModuleAppletDefault class (same, as URL extensions).
		 * Other variables is just shared variables which is used in several requests and should
		 * be instantiated outside of any try-catch block because of this.
		 */
		
		boolean anyChanges = false;
		int retryNumber = 0;
		URL myCheckUrl;
		HttpURLConnection connection;
		BufferedReader br;
		String date = "";
		
		/**
		 * Tries to check last modification date.
		 * First, we add python date modification check script extension to file address at server.
		 * After connection we read current modification date from server and also write modifica-
		 * tion date from fiven applet parameters. If strings are not the same - set anyChanges
		 * flag into true - this will indicate to next block (lock check) that modification date
		 * also was changed. According to this parameter value we will change current state and
		 * show appropriate message to application user. After all we are saving new modification
		 * date to application settings repository.
		 */
		
		retryNumber = 0; //used in case of network timeout
		try{
			myCheckUrl = new URL(file.getDownloadURL()+file.getModule().URL_CHECK_DATE);
			connection = (HttpURLConnection) myCheckUrl.openConnection();
			connection.setConnectTimeout(3000);
			connection.setReadTimeout(3000);
			br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
			date = br.readLine();
			String lastDate = file.getModule().getSettings().getParameter("fileModified");
			connection.disconnect();
			//check modification dates
			if(date.compareTo(lastDate) != 0){
				anyChanges = true;
				file.getModule().getSettings().setRuntimeParameter("fileModified", date);
			}
		}
		
		/**
		 * Catches passed invalid URL string.
		 */
		
		catch(MalformedURLException e){
			StateError newState = new StateError(this.file.getCurrentState().getIterator(), this.file);
			newState.setup("FATAL SYSTEM ERROR");
			this.file.setCurrentState(newState);
			return false;
		}
		
		/**
		 * Catches lost request, request rejection or server response error.
		 */
		
		catch(ConnectException e){
			StateError newState = new StateError(this.file.getCurrentState().getIterator(), this.file);
			newState.setup("SERVER NOT FOUND");
			return false;
		}
		
		/**
		 * Catches network timeout - tries to retry request 5 times before showing error message.
		 */
		
		catch(SocketTimeoutException e){
			retryNumber++;
			if(retryNumber > 5){
				StateError newState = new StateError(this.file.getCurrentState().getIterator(), this.file);
				newState.setup("CONNECTION TIMEOUT");
				this.file.setCurrentState(newState);
				return false;
			}
		}
		
		/**
		 * Catches IO exceptions (usually - everything what related with connection errors).
		 */
		
		catch(IOException e){
			StateError newState = new StateError(this.file.getCurrentState().getIterator(), this.file);
			newState.setup("CONNECTION ERROR");
			this.file.setCurrentState(newState);
			return false;
		}
		
		/**
		 * Check and save lock.
		 * First we add lock check extension to our current file url (it is python script on ser-
		 * ver side. Then we set timeout, so if request will take too much time - our application
		 * will not freeze for indefinite time. Response from server - one line, but that line can
		 * consist of several parameters separated by #### sign. Before processing response we should
		 * separate this (possible) parameters and check what is it.
		 * First parameter is always 1 (indicates, that file is locked) or 0 (if file is not locked
		 * or lock can be stolen).
		 * Second parameter (optional) unveils name of user, which made lock. This name is saved to
		 * parameters list CAPITALIZED.
		 * If our check shows, that file is currently locked, then we change current state to Locked.
		 * Otherwise, if anyChanges trigger indicates, that there was change of modification date -
		 * current state is changed to Updated. In case if there was no changes in modification date
		 * and file is not locked this moment - just change to Idling state.
		 */
		
		retryNumber = 0; //used in case of retry timeout
		try{
			//connection
			myCheckUrl = new URL(file.getDownloadURL()+file.getModule().URL_CHECK_LOCK);
			connection = (HttpURLConnection) myCheckUrl.openConnection();
			connection.setConnectTimeout(3000);
			connection.setReadTimeout(3000);
			br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
			String lock = br.readLine();
			connection.disconnect();
			//splitting of parameters
			String[] lockArray = lock.split("####");
			//file is locked
			if(lockArray[0].compareTo("1") == 0){
				//we know lock author name
				if(lockArray.length == 2){
					file.getModule().getSettings().setRuntimeParameter("lockAuthor", lockArray[1].toUpperCase());
				}
				anyChanges = true;
				file.getModule().getSettings().setRuntimeParameter("fileLocked", "1");
				//current state is to be changed
				if(!(currentState instanceof StateIdlingLocked)){
					currentState = new StateIdlingLocked(currentState.getIterator(), this.file);
					this.file.setCurrentState(currentState);
				}
				//state not changed - just restore former labels
				else{
					currentState.setLabel(currentLabel);
					currentState.setProgress(currentProgress);
				}
			}
			//file is not locked
			else{
				file.getModule().getSettings().setRuntimeParameter("fileLocked", "0");
				//file was modified from last check
				if(anyChanges){
					//current state is to be changed
					if(!(currentState instanceof StateIdlingUpdated)){
						currentState = new StateIdlingUpdated(currentState.getIterator(), this.file);
						this.file.setCurrentState(currentState);
					}
					//state not changed - just restore former labels
					else{
						currentState.setLabel(currentLabel);
						currentState.setProgress(currentProgress);
					}
				}
				//file was not modified
				else{
					//current state is to be changed
					if(!anyChanges && !(currentState instanceof StateIdling)){
						currentState = new StateIdling(currentState.getIterator(), this.file);
						this.file.setCurrentState(currentState);
					}
					//state not changed - just restore former labels
					else{
						currentState.setLabel(currentLabel);
						currentState.setProgress(currentProgress);
					}
				}
			}
		}

		/**
		 * Catches passed invalid URL string.
		 */
		
		catch(MalformedURLException e){
			StateError newState = new StateError(this.file.getCurrentState().getIterator(), this.file);
			newState.setup("FATAL SYSTEM ERROR");
			this.file.setCurrentState(newState);
			return false;
		}
		
		/**
		 * Catches lost request, request rejection or server response error.
		 */
		
		catch(ConnectException e){
			StateError newState = new StateError(this.file.getCurrentState().getIterator(), this.file);
			newState.setup("SERVER NOT FOUND");
			return false;
		}
		
		/**
		 * Catches network timeout - tries to retry request 5 times before showing error message.
		 */
		
		catch(SocketTimeoutException e){
			retryNumber++;
			if(retryNumber > 5){
				StateError newState = new StateError(this.file.getCurrentState().getIterator(), this.file);
				newState.setup("CONNECTION TIMEOUT");
				this.file.setCurrentState(newState);
				return false;
			}
		}
		
		/**
		 * Catches IO exceptions (usually - everything what related with connection errors)
		 */
		
		catch(IOException e){
			StateError newState = new StateError(this.file.getCurrentState().getIterator(), this.file);
			newState.setup("CONNECTION ERROR");
			this.file.setCurrentState(newState);
			return false;
		}
		
		/**
		 * End of execution.
		 * Mark in Execution invoker, that this thread is almost ended it's workand next execu-
		 * tion (from queue) can be executed.
		 */
		
		execution = false;
		file.getModule().getInvoker().setExecution(execution);
		return true;
		
	}

	/**
	 * Method for command execution invocation in separated thread
	 */
	public void run() {
		execute();
	}

}
